package com.yixing.tech.common.service;

import com.alibaba.fastjson.JSONObject;
import com.yixing.tech.common.base.BusinessException;
import com.yixing.tech.common.convert.ConvertUtils;
import com.yixing.tech.common.enums.ErrCode;
import com.yixing.tech.common.entity.WSContext;
import com.yixing.tech.common.utils.CommonConstant;
import com.yixing.tech.common.utils.CommonUtil;
import com.yixing.tech.common.vo.s3.UserVO;
import com.mongodb.client.result.UpdateResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.axis.client.Call;
import org.apache.axis.client.Service;
import org.apache.axis.encoding.ser.BeanDeserializerFactory;
import org.apache.axis.encoding.ser.BeanSerializerFactory;
import org.apache.axis.message.SOAPHeaderElement;
import org.bson.Document;
import org.bson.types.ObjectId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationOperation;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.xml.namespace.QName;
import javax.xml.rpc.ServiceException;
import java.rmi.RemoteException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;


/**
 * WmsService
 *
 * @author yixing tech
 * @since 1.0.0
 */
@Slf4j
public abstract class AbstractWmsService {

    @Value("${tenant-id: 6204cb763da841718ddd1cf8}")
    protected String tenantId;

    protected MongoTemplate mongoTemplate;
    protected StringRedisTemplate stringRedisTemplate;

    @Autowired
    public void setMongoTemplate(MongoTemplate mongoTemplate) {
        this.mongoTemplate = mongoTemplate;
    }

    @Autowired
    public void setStringRedisTemplate(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    protected void assertBiz(Supplier<Boolean> supplier, String message) throws BusinessException {
        if (supplier.get()) {
            throw new BusinessException(message);
        }
    }

    protected void assertBiz(Supplier<Boolean> supplier, String message, String... args) throws BusinessException {
        if (supplier.get()) {
            throw new BusinessException(String.format(message, args));
        }
    }

    protected Criteria newCriteria() {
        return Criteria.where("isDelete").ne(1);
    }

    protected Document simpleFindOne(String tableName, Object... params) {
        Criteria criteria = newCriteria();
        for (int i = 0; i < params.length; i += 2) {
            criteria.and(ConvertUtils.convertIfPossible(params[i], String.class)).is(params[i + 1]);
        }
        return mongoTemplate.findOne(Query.query(criteria), Document.class, tableName);
    }

    protected List<Document> simpleFind(String tableName, Object... params) {
        Criteria criteria = newCriteria();
        for (int i = 0; i < params.length; i += 2) {
            criteria.and(ConvertUtils.convertIfPossible(params[i], String.class)).is(params[i + 1]);
        }
        return mongoTemplate.find(Query.query(criteria), Document.class, tableName);
    }

    protected <T> T getEmbedded(Document document, Class<T> tClass, String... keys) {
        return document.getEmbedded(Arrays.asList(keys), tClass);
    }

    protected Document aggregateFirstResult(List<AggregationOperation> operations, String tableName) {
        List<Document> mappedResults = mongoTemplate.aggregate(Aggregation.newAggregation(operations), tableName, Document.class).getMappedResults();
        if (CollectionUtils.isEmpty(mappedResults)) {
            return null;
        }
        return mappedResults.get(0);
    }

    protected List<Document> aggregateResult(List<AggregationOperation> operations, String tableName) {
        List<Document> mappedResults = mongoTemplate.aggregate(Aggregation.newAggregation(operations), tableName, Document.class).getMappedResults();
        if (CollectionUtils.isEmpty(mappedResults)) {
            return Collections.emptyList();
        }
        return mappedResults;
    }

    protected boolean updateById(String id, Update update, String tableName) {
        UpdateResult re = mongoTemplate.updateFirst(Query.query(Criteria.where("_id").is(new ObjectId(id))), update, tableName);
        return re.getModifiedCount() > 0;
    }

    protected boolean updateById(Document doc, Update update, String tableName) {
        UpdateResult re = mongoTemplate.updateFirst(Query.query(Criteria.where("_id").is(new ObjectId(doc.getString("id")))), update, tableName);
        return re.getModifiedCount() > 0;
    }

    /**
     * 乐观锁更新
     */
    protected boolean versionUpdateById(Document doc, Update update, String tableName) {
        Integer version = ConvertUtils.convertIfPossible(doc.get("version"), Integer.class);
        Criteria criteria = newCriteria().and("_id").is(new ObjectId(doc.getString("id")));
        if (version != null) {
            criteria = criteria.and("version").is(version);
        }
        Query query = Query.query(criteria);
        update.set("version", (version == null ? 0 : version) + 1);
        UpdateResult re = mongoTemplate.updateFirst(query, update, tableName);
        return re.getModifiedCount() > 0;
    }

    /**
     * 乐观锁更新
     */
    protected boolean versionUpdate(Query query, Update update, String tableName) throws BusinessException {
        Document one = mongoTemplate.findOne(query, Document.class, tableName);
        if (one == null) {
            throw new BusinessException("更新条目不存在");
        }
        return versionUpdateById(one, update, tableName);
    }

    protected Document newDocument(Object... params) {
        Document document = new Document();
        for (int i = 0; i < params.length; i += 2) {
            document.put(ConvertUtils.convertIfPossible(params[i], String.class), params[i + 1]);
        }
        return document;
    }

    public UserVO getApiUser() throws BusinessException {
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        String apiToken = request.getHeader("x-user");
        if (CommonUtil.stringIsBlank(apiToken)) {
            throw new BusinessException(ErrCode.TOKEN_INVALID);
        } else {
            String json = this.stringRedisTemplate.opsForValue().get(CommonConstant.USERTOKEN + apiToken);
            if (ObjectUtils.isEmpty(json)) {
                throw new BusinessException(ErrCode.TOKEN_INVALID);
            } else {
                UserVO user = JSONObject.parseObject(json, UserVO.class);
                this.stringRedisTemplate.expire(CommonConstant.USERTOKEN + apiToken, 604800L, TimeUnit.SECONDS);
                return user;
            }
        }
    }

    public String getIp() {
        String ip = "未知IP";
        try {
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
            ip = request.getHeader(CommonConstant.HEADERS_IP);
            if (CommonUtil.stringIsBlank(ip)) {
                ip = request.getRemoteAddr();
            }
        } catch (Throwable ignore) {
            log.warn("获取IP失败。", ignore);
        }
        return ip;
    }

    protected Call erpLogin2(String loginUrl) throws BusinessException {
        //调用登陆接口
        Service s = new Service();
        Call call;
        try {
            call = (Call) s.createCall();
        } catch (ServiceException e) {
            throw new BusinessException("Service#createCall() failed", e);
        }
        call.setOperationName("login");
        call.setTargetEndpointAddress(loginUrl);
        QName wsContext = new QName("urn:client", "WSContext");
        call.setReturnType(wsContext);
        call.setReturnClass(WSContext.class);
        call.setReturnQName(new QName("", "loginReturn"));
        call.registerTypeMapping(WSContext.class, wsContext,
                new BeanSerializerFactory(WSContext.class, wsContext),
                new BeanDeserializerFactory(WSContext.class, wsContext));
        //超时
        call.setTimeout(Integer.valueOf(1000 * 600000 * 60));
        call.setMaintainSession(true);
        //登陆接口参数
        Object result = null;
        try {
            result = call.invoke(new Object[]{"18736829731", "qwer1312", "eas", "TEST0029", "L2", 2});
        } catch (RemoteException e) {
            throw new BusinessException("call#login erp failed", e);
        }
        WSContext rs = (WSContext) result;
        if (rs.getSessionId() == null) {
            throw new BusinessException("login fail");
        }
        log.info("login erp success, session: {}", rs.getSessionId());
        call.clearOperation();
        SOAPHeaderElement header = new SOAPHeaderElement("http://login.webservice.bos.kingdee.com",
                "SessionId", rs.getSessionId());
        call.addHeader(header);
        return call;
    }

//    /**
//     * @param webServiceUrl    创建服务地址WebService的URL,注意不是WSDL的URL
//     * @param action           确定调用方法（wsdl 命名空间地址 (wsdl文档中的targetNamespace) 和 方法名称 的组合）
//     * @param uri              即为wsdl文档的targetNamespace，命名空间
//     * @param methodName       请求方法
//     * @param webServiceEntity webServiceEntity 的子类
//     * @return
//     */
//    protected JSON doWebService(String webServiceUrl, String action, String uri,
//                                String methodName,
//                                WebServiceEntity webServiceEntity) {
//        OMElement methodEle = null;
//        JSON json = null;
//        Document logDoc = new Document();
//        try {
//
//            logDoc.put("路径", webServiceUrl + ": " + methodName);
//            logDoc.put("createTime", new Date());
//            logDoc.put("createName", "WMS");
//            logDoc.put("tenantId", tenantId);
//            logDoc.put("IP地址", getIp());
//            logDoc.put("请求类型", "WebService");
//            logDoc.put("类型", "三方");
//
//            ServiceClient serviceClient = new ServiceClient();
//            EndpointReference targetEPR = new EndpointReference(webServiceUrl);
//            Options options = serviceClient.getOptions();
//            options.setTo(targetEPR);
//            options.setAction(action);
//
//            OMFactory fac = OMAbstractFactory.getOMFactory();
//            /*
//             * 指定命名空间，参数：
//             * uri--即为wsdl文档的targetNamespace，命名空间
//             * perfix--可不填
//             */
//            OMNamespace omNs = fac.createOMNamespace(uri, "");
//            // 指定方法
//            methodEle = fac.createOMElement(methodName, omNs);
//            // 指定方法的参数
//            List<OMElement> elements = webServiceEntity.genOMElements(fac, omNs);
//            for (OMElement element : elements) {
//                methodEle.addChild(element);
//            }
//            methodEle.build();
//
//            //远程调用web服务
//            OMElement result = serviceClient.sendReceive(methodEle);
//            log.info("doWebService请求，方法: [{}]，结果: [{}]", methodEle, result);
//
//            XMLSerializer xmlSerializer = new XMLSerializer();
//            json = xmlSerializer.read(result.toString());
//            logDoc.put("结果", result.toString());
//        } catch (AxisFault axisFault) {
//            log.info("doWebService请求，方法: [{}]，发生异常。", methodEle, axisFault);
//            logDoc.put("结果", String.format("异常: %s", axisFault.getMessage()));
//        } finally {
//            mongoTemplate.insert(logDoc, "entity_" + WmsConstant.LOG_TABLE);
//        }
//        return json;
//    }

}
